﻿using System;
using System.Collections.Generic;

using System.Text;
using ITAP.Core.Modelling.Primitives;
using ITAP.ShprossesBuilder.Calculations;
using System.Diagnostics;
using ITAP.Core.Modelling;
using ITAP.Core.Calculations;

namespace ITAP.ShprossesBuilder.Modelling.Components.Constraints
{
    [Serializable]
    public class CompositeComponentCutAngleAndAngleIntervalConstraint : IConstraint
    {
        private bool enabled = true;
        private Type typeOfConnectedComponent;
        private AngleInterval allowedAngleInterval;
        #region IConstraint Members

        public CompositeComponentCutAngleAndAngleIntervalConstraint(Type typeOfConnectedComponent, AngleInterval allowedAngleInterval)
        {
            this.typeOfConnectedComponent = typeOfConnectedComponent;
            this.allowedAngleInterval = (AngleInterval)allowedAngleInterval.Clone();
        }

        public bool Check(IComponent component)
        {
            if (component.InnerComponents.Count == 0)
                return true;

            // TODO !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
            return false;

            foreach (IComponent connectedComponent in component.ConnectedComponents)
            {
                ModelPoint[] points;
                if (!(connectedComponent is IEndedComponent))
                    continue;
                if (connectedComponent.GetType() != typeOfConnectedComponent)
                    continue;

                IEndedComponent endedConnectedComponent = connectedComponent as IEndedComponent;

                IWidePrimitive connectedWidePrimitive = connectedComponent.Primitives[0] as IWidePrimitive;

                if (endedConnectedComponent.StartConnectedComponent == component)
                {
                    points = this.FindNearestIntersectionPoints(component, endedConnectedComponent, EndType.Start);
                    if (!points[2].Equals(connectedWidePrimitive.StartPoint) ||
                        !points[5].Equals(connectedWidePrimitive.DownBorder.StartPoint) ||
                        !points[8].Equals(connectedWidePrimitive.UpBorder.StartPoint))
                    {
                        return false;
                    }

                }
                else if (endedConnectedComponent.EndConnectedComponent == component)
                {
                    points = this.FindNearestIntersectionPoints(component, endedConnectedComponent, EndType.End);
                    if (!points[2].Equals(connectedWidePrimitive.EndPoint) ||
                        !points[5].Equals(connectedWidePrimitive.DownBorder.EndPoint) ||
                        !points[8].Equals(connectedWidePrimitive.UpBorder.EndPoint))
                    {
                        return false;
                    }
                }
                else
                    throw new Exception();
            }
            return true;
        }

        public void Fix(IComponent component)
        {
            if (!enabled) return;
            if (component.InnerComponents.Count == 0)
                return;
            List<IComponent> componentsToDisconnect = new List<IComponent>();
            foreach (IComponent connectedComponent in component.ConnectedComponents)
            {
                ModelPoint[] points;
                if (!(connectedComponent is IEndedComponent))
                    continue;
                if (connectedComponent.GetType() != typeOfConnectedComponent)
                    continue;
                IEndedComponent endedConnectedComponent = connectedComponent as IEndedComponent;
                IWidePrimitive connectedWidePrimitive = connectedComponent.Primitives[0] as IWidePrimitive;
                //if (endedConnectedComponent.StartConnectedComponent == component)
                //{
                //    points = this.FindNearestIntersectionPoints(component, endedConnectedComponent, EndType.Start);
                //    if (!points[2].Equals(connectedWidePrimitive.StartPoint))
                //    {
                //        connectedWidePrimitive.StartPoint = (ModelPoint)points[2].Clone();
                //    }
                //    if (!points[5].Equals(connectedWidePrimitive.DownBorder.StartPoint))
                //    {
                //        connectedWidePrimitive.DownBorder.StartPoint = (ModelPoint)points[5].Clone();
                //        connectedWidePrimitive.Update();
                //    }
                //    if (!points[8].Equals(connectedWidePrimitive.UpBorder.StartPoint))
                //    {
                //        connectedWidePrimitive.UpBorder.StartPoint = (ModelPoint)points[8].Clone();
                //        connectedWidePrimitive.Update();
                //    }

                //}
                //else if (endedConnectedComponent.EndConnectedComponent == component)
                //{
                //    points = this.FindNearestIntersectionPoints(component, endedConnectedComponent, EndType.End);
                //    if (!points[2].Equals(connectedWidePrimitive.EndPoint))
                //    {
                //        connectedWidePrimitive.EndPoint = (ModelPoint)points[2].Clone();
                //    }
                //    if (!points[5].Equals(connectedWidePrimitive.DownBorder.EndPoint))
                //    {
                //        connectedWidePrimitive.DownBorder.EndPoint = (ModelPoint)points[5].Clone();
                //        connectedWidePrimitive.Update();
                //    }
                //    if (!points[8].Equals(connectedWidePrimitive.UpBorder.EndPoint))
                //    {
                //        connectedWidePrimitive.UpBorder.EndPoint = (ModelPoint)points[8].Clone();
                //        connectedWidePrimitive.Update();
                //    }
                //}
                //else
                //    throw new Exception();                

                if (endedConnectedComponent.StartConnectedComponent == component)
                {
                    //ModelVector connectedComponentTangent = endedConnectedComponent.Primitives[0].GetTangent(0);
                    Angle angle;
                    points = this.FindNearestIntersectionPoints_Fast(component, endedConnectedComponent, EndType.Start, out angle);

                    if (points[0] == null || points[1] == null || points[2] == null)
                        continue;

                    if (!allowedAngleInterval.IsAngleInInterval(angle))
                    {
                        componentsToDisconnect.Add(endedConnectedComponent);
                        continue;
                    }

                    if (!points[0].Equals(connectedWidePrimitive.StartPoint))
                    {
                        connectedWidePrimitive.StartPoint = (ModelPoint)points[0].Clone();
                    }
                    if (!points[1].Equals(connectedWidePrimitive.DownBorder.StartPoint))
                    {
                        connectedWidePrimitive.DownBorder.StartPoint = (ModelPoint)points[1].Clone();
                        connectedWidePrimitive.Update();
                    }
                    if (!points[2].Equals(connectedWidePrimitive.UpBorder.StartPoint))
                    {
                        connectedWidePrimitive.UpBorder.StartPoint = (ModelPoint)points[2].Clone();
                        connectedWidePrimitive.Update();
                    }

                }
                else if (endedConnectedComponent.EndConnectedComponent == component)
                {
                    Angle angle;
                    points = this.FindNearestIntersectionPoints_Fast(component, endedConnectedComponent, EndType.End, out angle);

                    if (points[0] == null || points[1] == null || points[2] == null)
                        continue;

                    if (!allowedAngleInterval.IsAngleInInterval(angle))
                    {
                        componentsToDisconnect.Add(endedConnectedComponent);
                        continue;
                    }

                    if (!points[0].Equals(connectedWidePrimitive.EndPoint))
                    {
                        connectedWidePrimitive.EndPoint = (ModelPoint)points[0].Clone();
                    }
                    if (!points[1].Equals(connectedWidePrimitive.DownBorder.EndPoint))
                    {
                        connectedWidePrimitive.DownBorder.EndPoint = (ModelPoint)points[1].Clone();
                        connectedWidePrimitive.Update();
                    }
                    if (!points[2].Equals(connectedWidePrimitive.UpBorder.EndPoint))
                    {
                        connectedWidePrimitive.UpBorder.EndPoint = (ModelPoint)points[2].Clone();
                        connectedWidePrimitive.Update();
                    }
                }
                else
                    continue;
                    //throw new Exception();


            }

            foreach (IComponent componentToDisconect in componentsToDisconnect)
            {
                ConnectionProvider.Disconnect(component, componentToDisconect);
                if (componentToDisconect is SpacerConnector)
                {
                    ConnectionProvider.Disconnect(componentToDisconect, ((componentToDisconect as SpacerConnector).EndConnectedComponent));
                    //((componentToDisconect as SpacerConnector).EndConnectedComponent as Shpross).FitStartSpacerConnector();
                    //((componentToDisconect as SpacerConnector).EndConnectedComponent as Shpross).FitEndSpacerConnector();
                }
            }
        }

        private ModelPoint[] FindNearestIntersectionPoints_Fast(IComponent component, IEndedComponent connectedComponent, EndType endTypeOfConnectedComponent, out Angle angle)
        {
            ModelPoint[] result = new ModelPoint[6];
            angle = null;
            Angle _angle;
            double[] oldDistance = new double[6];
            double[] newDistance = new double[6];
            for (int i = 0; i < oldDistance.Length; i++)
                oldDistance[i] = double.MaxValue;

            for (int i = 0; i < newDistance.Length; i++)
                newDistance[i] = double.MaxValue;

            foreach (IComponent innerComponent in component.InnerComponents)
            {                
                if (!(innerComponent is IEndedComponent))
                    continue;

                // Примитив компонента, приконнекченного к дистанционной рамке
                IWidePrimitive connectedWidePrimitive = connectedComponent.Primitives[0] as IWidePrimitive;
                IWidePrimitive innerWidePrimitive = innerComponent.Primitives[0] as IWidePrimitive;
                ModelPoint[] currentPoints = new ModelPoint[6];
                ModelVector connectedComponentTangent = connectedWidePrimitive.GetTangent(0);
                switch (endTypeOfConnectedComponent)
                {
                    case EndType.Start:
                        if (innerComponent.Primitives[0].IsInTolerance((((IEndedPrimitive)connectedComponent.Primitives[0]).StartPoint), 3))
                        {
                            ModelPoint[] center = connectedWidePrimitive.IntersectMainLines(innerWidePrimitive.UpBorder, EndType.Start);
                            ModelPoint[] down = connectedWidePrimitive.DownBorder.IntersectMainLines(innerWidePrimitive.UpBorder, EndType.Start);
                            ModelPoint[] up = connectedWidePrimitive.UpBorder.IntersectMainLines(innerWidePrimitive.UpBorder, EndType.Start);
                            currentPoints[0] = center[0];
                            currentPoints[1] = down[0];
                            currentPoints[2] = up[0];
                            currentPoints[3] = center[1];
                            currentPoints[4] = down[1];
                            currentPoints[5] = up[1];

                            if (!double.IsNaN(currentPoints[0].X) && !double.IsNaN(currentPoints[0].Y) &&
                                !double.IsInfinity(currentPoints[0].X) && !double.IsInfinity(currentPoints[0].Y))
                            {
                                double offset = innerWidePrimitive.UpBorder.GetPathLengthFromProjectionToStart(currentPoints[0]) / innerWidePrimitive.UpBorder.Length;
                                if (!double.IsNaN(offset))
                                    _angle = ModelCalculations.GetAngleBetweenVectors(innerWidePrimitive.GetTangent(offset), connectedComponentTangent, false);
                                else
                                    continue;
                            }
                            else
                                continue;
                        }
                        else
                            continue;
                        break;

                    case EndType.End:
                        if (innerComponent.Primitives[0].IsInTolerance(((IEndedPrimitive)connectedComponent.Primitives[0]).EndPoint, 3))
                        {
                            ModelPoint[] center = connectedWidePrimitive.IntersectMainLines(innerWidePrimitive.UpBorder, EndType.End);
                            ModelPoint[] down = connectedWidePrimitive.DownBorder.IntersectMainLines(innerWidePrimitive.UpBorder, EndType.End);
                            ModelPoint[] up = connectedWidePrimitive.UpBorder.IntersectMainLines(innerWidePrimitive.UpBorder, EndType.End);
                            currentPoints[0] = center[0];
                            currentPoints[1] = down[0];
                            currentPoints[2] = up[0];
                            currentPoints[3] = center[1];
                            currentPoints[4] = down[1];
                            currentPoints[5] = up[1];

                            if (!double.IsNaN(currentPoints[0].X) && !double.IsNaN(currentPoints[0].Y) &&
                                !double.IsInfinity(currentPoints[0].X) && !double.IsInfinity(currentPoints[0].Y))
                            {
                                double offset = innerWidePrimitive.UpBorder.GetPathLengthFromProjectionToStart(currentPoints[0]) / innerWidePrimitive.UpBorder.Length;
                                if (!double.IsNaN(offset))
                                    _angle = ModelCalculations.GetAngleBetweenVectors(innerWidePrimitive.GetTangent(offset), connectedComponentTangent.Rotate(new Angle(Math.PI)), false);
                                else
                                    continue;
                            }
                            else
                                continue;
                        }
                        else
                            continue;
                        break;
                    default:
                        throw new Exception();
                }

                for (int i = 0; i < currentPoints.Length; i++)
                {
                    if (!double.IsNaN(currentPoints[i].X) && !double.IsNaN(currentPoints[i].Y) && !double.IsInfinity(currentPoints[i].X) && !double.IsInfinity(currentPoints[i].Y) && innerComponent.Primitives[0].IsInTolerance(currentPoints[i], 1))
                    {
                        if (endTypeOfConnectedComponent == EndType.Start)
                        {
                            ModelPoint point = null;
                            if (i == 0 || i == 3)
                                point = connectedWidePrimitive.StartPoint;
                            else if (i == 1 || i == 4)
                                point = connectedWidePrimitive.DownBorder.StartPoint;
                            else if (i == 2 || i == 5)
                                point = connectedWidePrimitive.UpBorder.StartPoint;

                            double _distance = ModelCalculations.GetDistance(point, result[i]);
                            oldDistance[i] = _distance;
                            _distance = ModelCalculations.GetDistance(point, currentPoints[i]);

                            newDistance[i] = _distance;
                        }
                        else if (endTypeOfConnectedComponent == EndType.End)
                        {

                            ModelPoint point = null;
                            if (i == 0 || i == 3)
                                point = connectedWidePrimitive.EndPoint;
                            else if (i == 1 || i == 4)
                                point = connectedWidePrimitive.DownBorder.EndPoint;
                            else if (i == 2 || i == 5)
                                point = connectedWidePrimitive.UpBorder.EndPoint;

                            double _distance = ModelCalculations.GetDistance(point, result[i]);
                            oldDistance[i] = _distance;
                            _distance = ModelCalculations.GetDistance(point, currentPoints[i]);
                            newDistance[i] = _distance;
                        }
                        else
                            throw new Exception();
                        if (newDistance[i] < oldDistance[i])
                        {
                            result[i] = (ModelPoint)currentPoints[i].Clone();
                            angle = _angle;
                        }
                    }
                }
             
            }
            return result;
        }

        private ModelPoint[] FindNearestIntersectionPoints(IComponent component, IEndedComponent connectedComponent, EndType endTypeOfConnectedComponent)
        {
            ModelPoint[] result = new ModelPoint[18];

            double[] oldDistance = new double[18];
            double[] newDistance = new double[18];

            for (int i = 0; i < oldDistance.Length; i++)
                oldDistance[i] = double.MaxValue;

            for (int i = 0; i < newDistance.Length; i++)
                newDistance[i] = double.MaxValue;

            foreach (IComponent innerComponent in component.InnerComponents)
            {
                if (!(innerComponent is IEndedComponent))
                    continue;

                ModelPoint[] currentPoints = null;

                IWidePrimitive connectedWidePrimitive = connectedComponent.Primitives[0] as IWidePrimitive;

                switch (endTypeOfConnectedComponent)
                {
                    case EndType.Start:
                        if (innerComponent.Primitives[0].IsInTolerance((((IEndedPrimitive)connectedComponent.Primitives[0]).StartPoint), 3))
                            currentPoints = connectedComponent.Primitives[0].Intersect(innerComponent.Primitives[0], EndType.Start);
                        else
                            continue;
                        break;

                    case EndType.End:
                        if (innerComponent.Primitives[0].IsInTolerance(((IEndedPrimitive)connectedComponent.Primitives[0]).EndPoint, 3))
                            currentPoints = connectedComponent.Primitives[0].Intersect(innerComponent.Primitives[0], EndType.End);
                        else
                            continue;

                        break;
                    default:
                        throw new Exception();
                }

                for (int i = 0; i < currentPoints.Length; i++)
                {
                    if (!double.IsNaN(currentPoints[i].X) && !double.IsNaN(currentPoints[i].Y) && !double.IsInfinity(currentPoints[i].X) && !double.IsInfinity(currentPoints[i].Y) && innerComponent.Primitives[0].IsInTolerance(currentPoints[i], 1))
                    {
                        if (endTypeOfConnectedComponent == EndType.Start)// && ((IEndedPrimitive)innerComponent.Primitives[0]).IsPointInSegment(currentPoints[i]))
                        {
                            ModelPoint point = null;
                            if (i < 3 || (i >= 9 && i < 12))
                                point = connectedWidePrimitive.StartPoint;
                            else if ((i >= 3 && i < 6) || (i >= 12 && i < 15))
                                point = connectedWidePrimitive.DownBorder.StartPoint;
                            else if ((i >= 6 && i < 9) || (i >= 15 && i < 18))
                                point = connectedWidePrimitive.UpBorder.StartPoint;

                            double _distance = ModelCalculations.GetDistance(point, result[i]);
                            oldDistance[i] = _distance;
                            _distance = ModelCalculations.GetDistance(point, currentPoints[i]);

                            newDistance[i] = _distance;
                        }
                        else if (endTypeOfConnectedComponent == EndType.End)// && ((IEndedPrimitive)innerComponent.Primitives[0]).IsPointInSegment(currentPoints[i]))
                        {

                            ModelPoint point = null;
                            if (i < 3 || (i >= 9 && i < 12))
                                point = connectedWidePrimitive.EndPoint;
                            else if ((i >= 3 && i < 6) || (i >= 12 && i < 15))
                                point = connectedWidePrimitive.DownBorder.EndPoint;
                            else if ((i >= 6 && i < 9) || (i >= 15 && i < 18))
                                point = connectedWidePrimitive.UpBorder.EndPoint;

                            double _distance = ModelCalculations.GetDistance(point, result[i]);
                            oldDistance[i] = _distance;
                            _distance = ModelCalculations.GetDistance(point, currentPoints[i]);
                            newDistance[i] = _distance;
                        }
                        else
                            throw new Exception();
                        if (newDistance[i] < oldDistance[i])
                            result[i] = (ModelPoint)currentPoints[i].Clone();
                    }
                }
            }
            //foreach (ModelPoint point in result)
            //    if (point == null)
                   // Debugger.Break();

            return result;
        }

        
        public bool Enabled
        {
            get
            {
                return enabled;
            }
            set
            {
                enabled = value;
            }
        }

        public ConstraintAction Action
        {
            get { return ConstraintAction.Fix; }
        }

        #endregion

        #region ICloneable Members

        public object Clone()
        {
            CompositeComponentCutAngleAndAngleIntervalConstraint clone = new CompositeComponentCutAngleAndAngleIntervalConstraint(typeOfConnectedComponent, allowedAngleInterval);
            return clone;
        }

        #endregion
    }
}
